import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Objects;

public abstract class MyInputStream implements Closeable {
    // MAX_SKIP_BUFFER_SIZE 用于确定跳过时使用的最大缓冲区大小
    private static final int MAX_SKIP_BUFFER_SIZE = 2048;
    // 默认的buffer size
    private static final int DEFAULT_BUFFER_SIZE = 8192;
    // JDK11中增加了一个nullInputStream，即空模式实现，以便可以直接调用而不用判空（可以看如下的补充说明）
    /**
     * 从输入流中读取下一个字节的数据。值作为范围为 0 到 255 的 int 返回。
     * 如果由于到达流的末尾而没有可用的字节，则返回 -1。
     * 此方法将阻塞，直到有输入数据可用、检测到流的末尾或引发异常。
     *
     * <p> 子类必须提供此方法的实现。
     *
     * @return 返回下一个字节数据，如果到达流末尾则返回 -1。
     * @exception IOException 如果发生 I/O 错误。
     * 读取下一个字符数据，没有返回-1
     */
    public abstract int read() throws IOException;
    /**
     * 从输入流中读取若干字节并将其存储到缓冲区数组 b 中。
     * 返回读取的字节数。如果 b 的长度为 0，则不读取任何字节并返回 0。
     * 如果没有可用的字节，因为流到达文件的末尾，则返回 -1。
     * 否则，将至少读取一个字节并存储到 b 中。
     *将读取到的数据放在 byte 数组中，该方法实际上调用read(byte b[], int off, int len)方法
     * @param b 将数据读取到其中的缓冲区。
     * @return 返回读取到缓冲区的总字节数，如果到达流末尾则返回 -1。
     * @exception IOException 如果由于任何原因第一个字节无法读取，流已关闭，或发生其他 I/O 错误。
     * @exception NullPointerException 如果 b 为 null。
     */
    public int read(byte b[]) throws IOException {
        return read(b, 0, b.length);
    }
    /**
     * 从输入流中读取最多 len 个字节的数据到字节数组中。
     * 此方法将阻塞，直到有输入数据可用、检测到文件结束或引发异常。
     *
     * @param b 将数据读取到其中的缓冲区。
     * @param off 在数组 b 中开始写入数据的偏移量。
     * @param len 要读取的最大字节数。
     * @return 返回读取到缓冲区的总字节数，如果到达流末尾则返回 -1。
     * @exception IOException 如果由于任何原因第一个字节无法读取，流已关闭，或发生其他 I/O 错误。
     * @exception NullPointerException 如果 b 为 null。
     * @exception IndexOutOfBoundsException 如果 off 或 len 为负数，或 len 大于 b.length - off。
     */
    public int read(byte b[], int off, int len) throws IOException {
        // 检查边界
        //Objects.checkFromIndexSize(off, len, b.length);
        //读取最大字节数为0返回
        if (len == 0) {
            return 0;
        }

        // 读取下一个字节
        int c = read();//
        /*
        这里的设计可以提前判断流状态，提前返回，提高效率
         */
        if (c == -1) { // 读到stream末尾，则返回读取的字节数量为-1
            return -1;
        }
        ///off 作为偏移量参数，指定了数据在数组 b 中的起始存储位置
        b[off] = (byte)c;// 将读取到的字节转换为byte类型，存入指定的数组位置b[off]

        // i用来记录取了多少个字节
        int i = 1;
        try {
            // 循环读取
            for (; i < len ; i++) {
                c = read();
                if (c == -1) {// 读到stream末尾，则break
                    break;
                }
                b[off + i] = (byte)c;
            }
        } catch (IOException ee) {
        }
        // 返回读取到的字节个数
        return i;
    }
    /**
     * 跳过并丢弃此输入流中的 n 个字节。
     * 该方法可能跳过更少的字节（甚至为 0），负数返回 0。返回实际跳过的字节数。
     *
     * @param n 要跳过的字节数。
     * @return 实际跳过的字节数。
     * @exception IOException 如果流不支持跳过操作，或发生其他 I/O 错误。
     */
    public long skip(long n) throws IOException {
        // 剩余要跳过的字节数
        long remaining = n;

        int nr;
        // 如果请求跳过的字节数小于等于0，则直接返回0
        if (n <= 0) {
            return 0;
        }
        // 设置缓冲区大小，取最小值
        // 使得最大不超过MAX_SKIP_BUFFER_SIZE，避免过大消耗内存
        int size = (int)Math.min(MAX_SKIP_BUFFER_SIZE, remaining);
        // 用于跳过字节的缓冲区
        byte[] skipBuffer = new byte[size];
        // 循环读取，直到跳过指定字节数或到达流末尾
        while (remaining > 0) {
           // 从流中读取最多size或remaining字节，读入skipBuffer，偏移量从0开始
            nr = read(skipBuffer, 0, (int)Math.min(size, remaining));
            // 如果读取结果为负，表示流已结束，跳出循环
            if (nr < 0) {
                break;
            }
            // 更新剩余字节数
            remaining = remaining - nr;
        }

        // 返回实际跳过的字节数（即总字节数n减去剩余字节数remaining）
        /*
假设 n = 15，MAX_SKIP_BUFFER_SIZE = 8，并且输入流中共有 10 个字节可供读取。

第一次循环：remaining = 15，缓冲区大小 size = 8。调用 read(skipBuffer, 0, 8)，假设读取 8 个字节，remaining 变为 15 - 8 = 7。
第二次循环：remaining = 7，缓冲区大小 size = 7。调用 read(skipBuffer, 0, 7)，假设读取了 2 个字节（流中只剩 2 个字节），remaining 变为 7 - 2 = 5。
第三次循环：remaining = 5，调用 read(skipBuffer, 0, 5)，此时返回 -1（流末尾），循环终止。

也就是read读到了文件末尾了，你remaining都没读到n的大小-就有了实际跳过数
         */
        return n - remaining;
    }
    /**
     * 返回在不阻塞的情况下可以从此输入流中读取的字节数的估计值。
     *
     * @return 可读取（或跳过）字节数的估计值，当到达输入流末尾时为 0。
     * @exception IOException 如果发生 I/O 错误。
     */
    public int available() throws IOException {
        return 0;
    }
    /**
     * 关闭此输入流并释放与流关联的任何系统资源。
     *
     * @exception IOException 如果发生 I/O 错误。
     */
    public void close() throws IOException {}
    /**
     * 标记输入流中的当前位置。后续调用 reset 方法将重新定位此流。
     * mark 方法不进行任何操作。
     *
     * @param readlimit 在标记位置失效之前可以读取的最大字节数。
     */
    public synchronized void mark(int readlimit) {}

    /**
     * 将流重新定位到最后一次调用 mark 方法时的位置。
     * 此方法对 InputStream 类无效，仅抛出 IOException。
     *  标记读取位置，下次还可以从这里开始读取，使用前要看当前流是否支持，可以使用 markSupport() 方法判断
     * @exception IOException 如果未标记或标记已失效。
     */

    // 重置读取位置为上次 mark 标记的位置
    public synchronized void reset() throws IOException {
        throw new IOException("mark/reset not supported");
    }
    // 判断当前流是否支持标记流，和上面两个方法配套使用。默认是false，由子类方法重写
    /**
     * 测试此输入流是否支持 mark 和 reset 方法。
     *
     * @return 如果此流实例支持 mark 和 reset 方法则返回 true；否则返回 false。
     */
    public boolean markSupported() {
        return false;
    }




}
